package src

import (
	"encoding/json"
	"strings"
	"sync"
	"time"

	"gitee.com/haifengat/goctp/v2"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"

	zd "gitee.com/haifengat/zorm-dm/v2"
)

var (
	instLastMin         sync.Map // 合约:map[string]interface{},最后1分钟数据
	mapInstrumentStatus sync.Map // 合约交易状态

	tradingDay       string                                              // 当前交易日
	actionDay        string                                              // 交易日起始交易日期
	actionDayNext    string                                              // 交易日起始交易日期-下一日
	instrumentStatus = map[string]goctp.TThostFtdcInstrumentStatusType{} // 合约状态
	trd              *goctp.TradePro
	md               *goctp.QuotePro
	cntTicks         = 0 // 记录tick数量
)

func redisToPg() (err error) {
	// 取交易日
	tradingDay, err := rdb.HGet(ctxRedis, "tradingday", "curday").Result()
	if err != nil {
		return errors.Wrap(err, "get curday")
	}
	// 删除当前交易日数据(实现重复入库)
	_, err = zd.Delete[Bar](ctxDAO, map[string]any{"tradingday": tradingDay})
	if err != nil {
		return errors.Wrap(err, "delete")
	}
	// 取所有 key
	var bars = make([]Bar, 0, 5000)
	insts, err := rdb.Keys(ctxRedis, "*").Result()
	if err != nil {
		return errors.Wrap(err, "get redis keys")
	}
	// 按合约key入库
	for _, inst := range insts {
		if inst == "tradingday" {
			continue
		}
		var mins = []string{}
		if mins, err = rdb.LRange(ctxRedis, inst, 0, -1).Result(); err != nil {
			logrus.Error("取redis数据错误:", inst, err)
			continue
		}
		for _, bsMin := range mins {
			var bar Bar
			bsMin = strings.ReplaceAll(bsMin, "id", "DateTime")
			if err = json.Unmarshal([]byte(bsMin), &bar); err != nil {
				logrus.Error("解析bar错误:", bar, " ", err)
				continue
			}
			// bar.InsertTime = DateTimeType(time.Now().Local().Format(time.DateTime))
			// bar.UpdateTime = DateTimeType(time.Now().Local().Format(time.DateTime))
			bars = append(bars, *bar.Fix())
			if len(bars) >= 5000 { // 10W -> PostgreSQL only supports 65535 parameters(ZORM)
				n, err := zd.Insert(ctxDAO, false, bars...)
				if err != nil {
					return errors.Wrap(err, "create batch")
				}
				logrus.Infof("inserted bars: %d", n)
				bars = make([]Bar, 0, 5000)
			}
		}
	}
	if len(bars) > 0 {
		n, err := zd.Insert(ctxDAO, false, bars...)
		if err != nil {
			return errors.Wrap(err, "create batch")
		}
		logrus.Info("inserted bars: ", n)
	}
	return
}

func save2DB() {
	if trd != nil && trd.IsLogin {
		// 查询数据并入库
		mpAccount, err := trd.ReqQryTradingAccount()
		if err != nil {
			logrus.Error("查询资金错误:", err.Error())

		} else {
			for _, acc := range mpAccount {
				field := new(AccountField).FromCTP(acc)
				field.InsertTime = DateTimeType(time.Now().Local().Format(time.DateTime))
				field.UpdateTime = DateTimeType(time.Now().Local().Format(time.DateTime))
				_, err := zd.UpdateAndInsert[AccountField](ctxDAO, zd.StructToMap(ctxDAO, *field, false, true))
				if err != nil {
					logrus.Infof("%+v", field)
					logrus.Error("update account error:", err.Error())
				}
			}
		}
		orders := make([]map[string]any, 0)
		for _, ord := range trd.Orders {
			field := new(OrderField).FromCTP(ord)
			field.InsertTime = DateTimeType(time.Now().Local().Format(time.DateTime))
			field.UpdateTime = DateTimeType(time.Now().Local().Format(time.DateTime))
			orders = append(orders, zd.StructToMap(ctxDAO, *field, false, true))
		}
		if len(orders) > 0 {
			_, err := zd.UpdateAndInsert[OrderField](ctxDAO, orders...)
			if err != nil {
				logrus.Error("update order error:", err.Error())
			}
		}
		trades := make([]map[string]any, 0)
		for _, trds := range trd.Trades {
			for _, trd := range trds {
				field := new(TradeField).FromCTP(trd)
				field.InsertTime = DateTimeType(time.Now().Local().Format(time.DateTime))
				field.UpdateTime = DateTimeType(time.Now().Local().Format(time.DateTime))
				trades = append(trades, zd.StructToMap(ctxDAO, *field, false, true))
			}
		}
		if len(trades) > 0 {
			_, err := zd.UpdateAndInsert[TradeField](ctxDAO, trades...)
			if err != nil {
				logrus.Error("update trade error:", err.Error())
			}
		}
		posis := make([]map[string]any, 0)
		if ps, err := trd.ReqQryPosition(); err != nil {
			logrus.Error("查询持仓错误:", err.Error())
		} else {
			for _, p := range ps {
				field := new(PositionField).FromCTP(p)
				field.InsertTime = DateTimeType(time.Now().Local().Format(time.DateTime))
				field.UpdateTime = DateTimeType(time.Now().Local().Format(time.DateTime))
				posis = append(posis, zd.StructToMap(ctxDAO, *field, false, true))
			}
		}
		if len(posis) > 0 {
			_, err := zd.UpdateAndInsert[PositionField](ctxDAO, posis...)
			if err != nil {
				logrus.Error("update position error:", err.Error())
			}
		}
	}
}

func Run724(isTest bool) {
	initEnv() // 初始化环境

	// startTrade() // 测试

	logrus.Info("start CTP 交易员 7x24")
	for {
		// 7×24
		curDay := time.Now().Local().Format("20060102")
		// 当日是否为交易日
		n, err := zd.SelectCount[Calendar](ctxDAO, map[string]any{"TradingDay": curDay, "IsTrading": true})
		if err != nil {
			logrus.Error(err)
			time.Sleep(time.Minute * 5)
			continue
		}
		tm, _ := time.ParseInLocation("20060102150405", curDay+"084500", time.Local)
		if n == 0 { // 非交易日, 下一交易日再判断
			tm = tm.AddDate(0, 0, 1)
			logrus.Infof("%s 非交易日, 下一自然日再继续: %s", curDay, tm)
			time.Sleep(time.Until(tm))
			continue
		}
		// 08:45:00
		if time.Now().Local().Before(tm) {
			logrus.Infof("等到 %s 启动接口", tm)
			time.Sleep(time.Until(tm))
			logrus.Info("ctp starting ...")
			startTrade()
		}
		// 15:15:00
		tm, _ = time.ParseInLocation("20060102150405", curDay+"151500", time.Local)
		if time.Now().Local().Before(tm) {
			logrus.Infof("%s 之前, 启动接口", tm)
			logrus.Info("ctp starting ...")
			startTrade()
		}

		// 20:45:00
		tm, _ = time.ParseInLocation("20060102150405", curDay+"204500", time.Local)
		if time.Now().Local().Before(tm) {
			logrus.Infof("等到夜盘时间 %s 进行处理", tm)
			time.Sleep(time.Until(tm))
		}
		// 当日有夜盘(下一自然日==next,或者当日周五&next下周一)
		next, err := zd.SelectRow[Calendar](ctxDAO, "MIN(TradingDAY)", map[string]any{"IsTrading": true, "TradingDay": map[string]any{">": curDay}})
		if err != nil { // 数据查询有错误, 5分钟后重新查询
			logrus.Error(err)
			time.Sleep(time.Minute * 5)
			continue
		}
		nextDay := next.(string)
		nextTradingDay, _ := time.ParseInLocation("20060102", nextDay, time.Local)
		if nextDay == tm.AddDate(0, 0, 1).Format("20060102") || (tm.Weekday() == time.Friday && nextTradingDay.Weekday() == time.Monday) {
			logrus.Info("有夜盘, 启动接口")
			startTrade()
		}

		// 下一交易日 08:45:00
		tm, _ = time.ParseInLocation("20060102150405", nextDay+"084500", time.Local)
		logrus.Infof("等到下一交易日 %s, 重新启动接口", tm)
		time.Sleep(time.Until(tm))
	}
}
